package com.desaysv.svview.webkit

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.webkit.JsPromptResult
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import java.lang.reflect.Method

/**
 * @author linzi
 * @date 2023-5-8
 */
class SVWebView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = -1,
    defStyleRes: Int = -1
) : WebView(context, attrs, defStyleAttr, defStyleRes) {
    private val mProxy = LinkedHashMap<String, Any>()
    private var isSetWebClient=false
    private var isSetWebChormeClient=false

    init {
        initClient()
    }

    /**
     * 拦截映射操作
     */
    override fun addJavascriptInterface(`object`: Any, name: String) {
        mProxy[name] = `object`
    }

    /**
     * 不允许用户自行设置
     */
    @Deprecated("not allow init")
    override fun setWebViewClient(client: WebViewClient) {
    }
    /**
     * 不允许用户自行设置
     */
    @Deprecated("not allow init")
    override fun setWebChromeClient(client: WebChromeClient?) {
    }
    /**
     * 必须使用自定义
     */
    fun setSvWebViewClient(client: SvWebViewClient) {
        isSetWebClient=true
        super.setWebViewClient(client)
    }
    /**
     * 必须使用自定义
     */
    fun setSvWebChromeClient(client: SvWebChromeClient?) {
        isSetWebChormeClient=true
        super.setWebChromeClient(client)
    }
    /**
     * 初始化client对象
     */
    @SuppressLint("SetJavaScriptEnabled")
    private fun initClient() {
        settings.javaScriptEnabled=true
        if(!isSetWebClient) {
            super.setWebViewClient(object : SvWebViewClient(){})
        }
        if(!isSetWebChormeClient) {
            super.setWebChromeClient(object : SvWebChromeClient(){})
        }
    }

    companion object {
        private const val TAG = "[SVWebView]"
        /**
         * 协议头
         */
        private val SCHEME = "svwebJs://"
        /**
         * 需要过滤的部分函数
         */
        private val EXCLUED_METHOD= arrayOf("getClasses","getContext","getMethods","getClassLoader","newInstance")

        open class SvWebViewClient:WebViewClient(){
            override fun onPageFinished(view: WebView?, url: String?) {
                super.onPageFinished(view, url)
                addProxy(view)
            }

            /**
             * 注入代理
             */
            private fun addProxy(webview:WebView?) {
                webview?.let {
                    val proxy = (it as SVWebView).mProxy
                    for (key in proxy.keys) {
                        loadWindow(webview,key, proxy[key]!!)
                    }
                }

            }


            /**
             * 执行注入
             */
            private fun loadWindow(webview:WebView,windowName: String, obj: Any) {
                Log.e(TAG, "loadWindow: $windowName")
                val claz = obj.javaClass
                val methods = claz.methods
                val body = StringBuffer()
                for (method in methods) {
                    //找到需要注入的函数，必须带有Js注解的
                    if (method.isAnnotationPresent(Js::class.java)) {
                        //拿到参数数量
                        val types = method.genericParameterTypes
                        val paramsCount = types.size
                        //构建需要注入的js函数参数 function(arg1,arg2,...)
                        val args = StringBuffer()
                        //构建调用时传入的参数 key=arg1&key1=arg2
                        val argParams = StringBuffer()
                        for (index in 1..paramsCount) {
                            args.append("arg$index")
                            argParams.append("'${(types[index - 1] as Class<Any>).name}='+arg$index")
                            if (index < paramsCount) {
                                args.append(",")
                                argParams.append("+'&'+")
                            }
                        }
                        //如果需要代理的这个函数没有参数就直接注入对应的js,如果有参数则需要注入带参数的js调用
                        if (!TextUtils.isEmpty(argParams)) {
                            val item = """
                        $windowName.${method.name} = function(${args}){
                            console.log('call android method:[${method.name}]');
                            return window.prompt('$SCHEME${webview.context.packageName}/${windowName}/${method.name}?'+$argParams);
                        };
                      """.trimIndent()
                            body.append(item)
                        } else {
                            val item = """
                        $windowName.${method.name} = function(${args}){
                            console.log('call android method:[${method.name}]');
                            return window.prompt('$SCHEME${webview.context.packageName}/${windowName}/${method.name}');
                        };
                      """.trimIndent()
                            body.append(item)
                        }

                    }
                }
                /**
                 * javascript:
                let Android ={};
                Android.test = function(arg1,arg2){
                console.log('call android method:[test]');
                return window.prompt('svwebJs://com.desaysv.hicar/Android/test?'+'java.lang.String='+arg1+'&'+'java.lang.String='+arg2);
                };
                Android.test2 = function(arg1){
                console.log('call android method:[test2]');
                return window.prompt('svwebJs://com.desaysv.hicar/Android/test2?'+'java.lang.String='+arg1);
                };
                 */
                val windowJs = """
            javascript:
                    let $windowName ={};
                    $body
        """.trimIndent()
                Log.e(TAG, "loadWindow: $windowJs")
                webview?.loadUrl(windowJs)
            }
        }


        open class SvWebChromeClient:WebChromeClient(){
            override fun onJsPrompt(
                view: WebView?,
                url: String?,
                message: String?,
                defaultValue: String?,
                result: JsPromptResult?
            ): Boolean {
                view?.let {
                    if (!TextUtils.isEmpty(message) && message!!.startsWith(SCHEME + it.context.packageName)) {
                        dealProxy(it,message, result)
                        return true
                    }
                }
                return super.onJsPrompt(view, url, message, defaultValue, result)
            }

            /**
             * 处理方法代理，解析协议 svwebJs://包名/映射名/方法名?key=value&key1=value1
             */
            private fun dealProxy(webview:WebView,msg: String, result: JsPromptResult?) {
                var proxy = (webview as SVWebView).mProxy
                val params = msg.substring((SCHEME + webview.context.packageName + "/").length, msg.length)
                Log.e(TAG, "dealProxy: $params")
                val args = params.split("/")
                //获取映射的类名
                val windowName = args[0]
                val kv = args[1].split("?")
                //获取隐射的方法名
                val methodName = kv[0]
                Log.e(TAG, "dealProxy: $methodName")
                //过滤掉禁止参与映射代理的函数，不允许通过映射代理，避免遭受攻击
                if(EXCLUED_METHOD.contains(methodName)){
                    return
                }
                var method: Method? = null
                for (mod in proxy[windowName]!!.javaClass.methods) {
                    //不仅是函数名要匹配，代理的函数还必须是被Js注解标记的函数才能接受代理
                    if (mod.name == methodName&&mod.isAnnotationPresent(Js::class.java)) {
                        method = mod
                        break
                    }
                }
                method?.let {
                    it.isAccessible = true
                    if (kv.size > 1) {
                        val argsParams = kv[1].split("&")
                        val argTypes = ArrayList<Any>()
                        //解析参数
                        for (argsParam in argsParams) {
                            Log.i(TAG, "dealProxy: $argsParam")
                            val keyValues = argsParam.split("=")
                            //将对应参数转为对应类型
                            when (keyValues[0]) {
                                "java.lang.String" -> {
                                    argTypes.add(keyValues[1])
                                }

                                "String" -> {
                                    argTypes.add(keyValues[1])
                                }

                                "java.lang.Integer" -> {
                                    argTypes.add(keyValues[1].toInt())
                                }

                                "int" -> {
                                    argTypes.add(keyValues[1].toInt())
                                }

                                "double" -> {
                                    argTypes.add(keyValues[1].toDouble())
                                }

                                "java.lang.Double" -> {
                                    argTypes.add(keyValues[1].toDouble())
                                }

                                "float" -> {
                                    argTypes.add(keyValues[1].toFloat())
                                }

                                "lava.lang.Float" -> {
                                    argTypes.add(keyValues[1].toFloat())
                                }

                                else -> {
                                    argTypes.add(keyValues[1])
                                }
                            }
                        }

                        val obj = invoke(proxy[windowName]!!, it, *(argTypes.toTypedArray()))
                        Log.e(TAG, "dealProxy: ${obj}")
                        result?.confirm(obj.toString())
                    } else {
                        val obj = invoke(proxy[windowName]!!, it)
                        result?.confirm(obj.toString())
                    }

                }
            }

            /**
             * 执行代理
             */
            private fun invoke(obj: Any, method: Method, vararg args: Any): Any? {
                Log.e(TAG, "invoke: ${args.size}")
                return method.invoke(obj, *args)
            }


        }

    }

    @Target(
        AnnotationTarget.FUNCTION,
        AnnotationTarget.PROPERTY_GETTER,
        AnnotationTarget.PROPERTY_SETTER
    )
    @Retention(AnnotationRetention.RUNTIME)
    annotation class Js(
    )
}